package cn.iocoder.yudao.module.signature.validatap12status.util;

import cn.hutool.core.codec.Base64Decoder;
import cn.hutool.core.codec.Base64Encoder;
import cn.hutool.core.date.DateUtil;
import cn.iocoder.yudao.module.signature.validatap12status.dto.CertInfoDTO;
import cn.iocoder.yudao.module.signature.validatap12status.dto.CertStatusDTO;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.ocsp.*;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.DigestCalculatorProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;

import javax.naming.InvalidNameException;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyStore;
import java.security.Security;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;

/**
 * @author liujq
 * @date 20241026 21:56
 */
public class OCSPUtil {

    //苹果根证书对象
    private static X509Certificate issuerCert;

    private static X509Certificate getIssuerCert() {
        if (issuerCert == null) {
            String issuerCertStr = "MIIEUTCCAzmgAwIBAgIQfK9pCiW3Of57m0R6wXjF7jANBgkqhkiG9w0BAQsFADBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcNMjAwMjE5MTgxMzQ3WhcNMzAwMjIwMDAwMDAwWjB1MUQwQgYDVQQDDDtBcHBsZSBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9ucyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTELMAkGA1UECwwCRzMxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJBgNVBAYTAlVTMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2PWJ/KhZC4fHTJEuLVaQ03gdpDDppUjvC0O/LYT7JF1FG+XrWTYSXFRknmxiLbTGl8rMPPbWBpH85QKmHGq0edVny6zpPwcR4YS8Rx1mjjmi6LRJ7TrS4RBgeo6TjMrA2gzAg9Dj+ZHWp4zIwXPirkbRYp2SqJBgN31ols2N4Pyb+ni743uvLRfdW/6AWSN1F7gSwe0b5TTO/iK1nkmw5VW/j4SiPKi6xYaVFuQAyZ8D0MyzOhZ71gVcnetHrg21LYwOaU1A0EtMOwSejSGxrC5DVDDOwYqGlJhL32oNP/77HK6XF8J4CjDgXx9UO0m3JQAaN4LSVpelUkl8YDib7wIDAQABo4HvMIHsMBIGA1UdEwEB/wQIMAYBAf8CAQAwHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wRAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzABhihodHRwOi8vb2NzcC5hcHBsZS5jb20vb2NzcDAzLWFwcGxlcm9vdGNhMC4GA1UdHwQnMCUwI6AhoB+GHWh0dHA6Ly9jcmwuYXBwbGUuY29tL3Jvb3QuY3JsMB0GA1UdDgQWBBQJ/sAVkPmvZAqSErkmKGMMl+ynsjAOBgNVHQ8BAf8EBAMCAQYwEAYKKoZIhvdjZAYCAQQCBQAwDQYJKoZIhvcNAQELBQADggEBAK1lE+j24IF3RAJHQr5fpTkg6mKp/cWQyXMT1Z6b0KoPjY3L7QHPbChAW8dVJEH4/M/BtSPp3Ozxb8qAHXfCxGFJJWevD8o5Ja3T43rMMygNDi6hV0Bz+uZcrgZRKe3jhQxPYdwyFot30ETKXXIDMUacrptAGvr04NM++i+MZp+XxFRZ79JI9AeZSWBZGcfdlNHAwWx/eCHvDOs7bJmCS1JgOLU5gm3sUjFTvg+RTElJdI+mUcuER04ddSduvfnSXPN/wmwLCTbiZOTCNwMUGdXqapSqqdv+9poIZ4vvK7iqF0mDr8/LvOnP6pVxsLRFoszlh6oKw0E6eVzaUDSdlTs=";
            try {
                byte[] issuerByte = Base64Decoder.decode(issuerCertStr);
                CertificateFactory cf = CertificateFactory.getInstance("X.509");
                issuerCert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(issuerByte));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return issuerCert;
    }


    public static CertStatusDTO checkCertStatus(String certStr, String pwd, int retry) {
        CertStatusDTO certStatusDTO = new CertStatusDTO();
        int resultNum;
        while (retry > 0) {
            try {
                byte[] p12Byte = Base64Decoder.decode(certStr);
                // 添加BouncyCastle支持
                Security.addProvider(new BouncyCastleProvider());
                // 加载证书
                KeyStore ks = KeyStore.getInstance("PKCS12", "BC");
                ks.load(new ByteArrayInputStream(p12Byte), pwd.toCharArray());
                Enumeration<String> aliasenum = null;
                aliasenum = ks.aliases();
                String keyAlias = "1";
                if (aliasenum.hasMoreElements()) {
                    keyAlias = aliasenum.nextElement();
                }
                X509Certificate cert = (X509Certificate) ks.getCertificate(keyAlias);
                OCSPReq ocspReq = GenOcspReq(cert, getIssuerCert());
                String ocspUrl = getOCSPUrl(cert);
                OCSPResp ocspResp = requestOCSPResponse(ocspUrl, ocspReq);
                int status = ocspResp.getStatus();
                if (OCSPResp.SUCCESSFUL == status) {
                    BasicOCSPResp basic = (BasicOCSPResp) ocspResp.getResponseObject();
                    SingleResp[] resps = basic.getResponses();
                    if (resps != null && resps.length == 1) {
                        SingleResp resp = resps[0];
                        CertificateStatus certStatus = resp.getCertStatus();
                        if (certStatus == CertificateStatus.GOOD) {
                            resultNum = CertStatusDTO.Status.VALID.getCode();
                            Date notAfter = cert.getNotAfter();
                            if (System.currentTimeMillis() > notAfter.getTime()) {
                                resultNum = CertStatusDTO.Status.EXPIRED.getCode();
                            }
                            certStatusDTO.setStatus(resultNum);
                        } else if (certStatus instanceof RevokedStatus) {
                            resultNum = CertStatusDTO.Status.BANNED.getCode();
                            RevokedStatus revokedStatus = (RevokedStatus) certStatus;
                            certStatusDTO.setCertRevokeTime(DateUtil.format(revokedStatus.getRevocationTime(), "yyyy-MM-dd HH:mm:ss"));
                            certStatusDTO.setCertRevokeReason(revokedStatus.getRevocationReason());
                            if (certStatusDTO.getCertRevokeReason() == 1) {
                                resultNum = CertStatusDTO.Status.REVOKED.getCode();
                            }
                            certStatusDTO.setStatus(resultNum);
                        } else if (certStatus instanceof UnknownStatus) {
                            resultNum = CertStatusDTO.Status.UNKNOWN.getCode();
                            certStatusDTO.setStatus(resultNum);
                        }
                        retry = 0;
                    }
                }

            } catch (Exception e) {
                e.printStackTrace();
                retry--;
            }
        }

        return certStatusDTO;
    }

    /**
     * 创建OCSP请求
     *
     * @param nextCert
     * @param nextIssuer
     * @return
     * @throws OCSPException
     * @throws OperatorCreationException
     * @throws CertificateEncodingException
     * @throws IOException
     */
    public static OCSPReq GenOcspReq(X509Certificate nextCert,
                                     X509Certificate nextIssuer) throws OCSPException, OperatorCreationException, CertificateEncodingException, IOException {
        OCSPReqBuilder ocspRequestGenerator = new OCSPReqBuilder();
        DigestCalculatorProvider digCalcProv = new JcaDigestCalculatorProviderBuilder().setProvider("BC").build();
        // 获取 certId
        CertificateID certId = new CertificateID(
                (new BcDigestCalculatorProvider())
                        .get(CertificateID.HASH_SHA1),
                new X509CertificateHolder(nextIssuer.getEncoded()),
                nextCert.getSerialNumber());
        ocspRequestGenerator.addRequest(certId);
        BigInteger nonce = BigInteger.valueOf(System.currentTimeMillis());
        Extension ext = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, false, new DEROctetString(nonce.toByteArray()));
        ocspRequestGenerator.setRequestExtensions(new Extensions(new Extension[]{ext}));
        return ocspRequestGenerator.build();
    }


    /**
     * 发送请求并接收返回值
     *
     * @param url     请求地址 从证书中获取
     * @param ocspReq 请求对象
     * @return
     * @throws IOException
     * @throws MalformedURLException
     */
    public static OCSPResp requestOCSPResponse(String url, OCSPReq ocspReq) throws IOException, MalformedURLException {
        byte[] ocspReqData = ocspReq.getEncoded();
        HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection();
        try {
            con.setRequestProperty("Content-Type", "application/ocsp-request");
            con.setRequestProperty("Accept", "application/ocsp-response");
            con.setDoInput(true);
            con.setDoOutput(true);
            con.setUseCaches(false);
            OutputStream out = con.getOutputStream();
            try {
                IOUtils.write(ocspReqData, out);
                out.flush();
            } finally {
                IOUtils.closeQuietly(out);
            }

            byte[] responseBytes = IOUtils.toByteArray(con.getInputStream());
            OCSPResp ocspResp = new OCSPResp(responseBytes);

            return ocspResp;
        } finally {
            if (con != null) {
                con.disconnect();
            }
        }
    }


    /**
     * 获取证书中 OSCP的请求地址
     *
     * @param certificate
     * @return
     * @throws IOException
     */
    public static String getOCSPUrl(X509Certificate certificate) throws IOException {
        ASN1Primitive obj;
        try {
            obj = getExtensionValue(certificate, Extension.authorityInfoAccess.getId());
        } catch (IOException ex) {
            return null;
        }

        if (obj == null) {
            return null;
        }

        AuthorityInformationAccess authorityInformationAccess = AuthorityInformationAccess.getInstance(obj);

        AccessDescription[] accessDescriptions = authorityInformationAccess.getAccessDescriptions();
        for (AccessDescription accessDescription : accessDescriptions) {
            boolean correctAccessMethod = accessDescription.getAccessMethod().equals(X509ObjectIdentifiers.ocspAccessMethod);
            if (!correctAccessMethod) {
                continue;
            }

            GeneralName name = accessDescription.getAccessLocation();
            if (name.getTagNo() != GeneralName.uniformResourceIdentifier) {
                continue;
            }

            DERIA5String derStr = DERIA5String.getInstance((ASN1TaggedObject) name.toASN1Primitive(), false);
            return derStr.getString();
        }

        return null;
    }

    /**
     * @param certificate the certificate from which we need the ExtensionValue
     * @param oid         the Object Identifier value for the extension.
     * @return the extension value as an ASN1Primitive object
     * @throws IOException
     */
    private static ASN1Primitive getExtensionValue(X509Certificate certificate, String oid) throws IOException {
        byte[] bytes = certificate.getExtensionValue(oid);
        if (bytes == null) {
            return null;
        }
        ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(bytes));
        ASN1OctetString octs = (ASN1OctetString) aIn.readObject();
        aIn = new ASN1InputStream(new ByteArrayInputStream(octs.getOctets()));
        return aIn.readObject();
    }

    /**
     * 修改P12证书密码
     *
     * @param base64P12       Base64编码的P12证书数据
     * @param currentPassword 当前密码
     * @return 新密码的P12证书Base64字符串
     */
    public static String changeP12Password(String base64P12, String currentPassword) throws Exception {
        // 解码 base64P12
        byte[] p12Bytes = Base64Decoder.decode(base64P12);

        // 使用当前密码解码 P12
        KeyStore ks = KeyStore.getInstance("PKCS12");
        ks.load(new ByteArrayInputStream(p12Bytes), currentPassword.toCharArray());

        // 获取证书和私钥
        Enumeration<String> aliases = ks.aliases();
        String alias = aliases.nextElement();
        X509Certificate cert = (X509Certificate) ks.getCertificate(alias);
        java.security.PrivateKey privateKey = (java.security.PrivateKey) ks.getKey(alias, currentPassword.toCharArray());

        // 创建新的KeyStore并设置新密码
        KeyStore newKs = KeyStore.getInstance("PKCS12");
        newKs.load(null, null);
        newKs.setKeyEntry(alias, privateKey, "1".toCharArray(), new java.security.cert.Certificate[]{cert});

        // 将新的KeyStore转换为字节数组
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        newKs.store(baos, "1".toCharArray());

        // 返回Base64编码的新P12数据
        return Base64Encoder.encode(baos.toByteArray());
    }

    /**
     * 获取证书的详细信息
     *
     * @param base64P12 Base64编码的P12证书数据
     * @param password  证书密码
     * @return 证书详细信息
     */
    public static CertInfoDTO getCertAllInfo(String base64P12, String password) throws Exception {
        // 解码 base64P12
        byte[] p12Bytes = Base64Decoder.decode(base64P12);
        // 添加BouncyCastle支持
        Security.addProvider(new BouncyCastleProvider());
        // 加载证书
        KeyStore ks = KeyStore.getInstance("PKCS12", "BC");
        ks.load(new ByteArrayInputStream(p12Bytes), password.toCharArray());

        // 获取证书
        Enumeration<String> aliases = ks.aliases();
        String alias = aliases.nextElement();
        X509Certificate cert = (X509Certificate) ks.getCertificate(alias);

        // 获取证书状态
        CertStatusDTO certStatusDTO = checkCertStatus(base64P12, password, 1);
        // 获取证书基本信息
        CertUtil.getCertInfo(base64P12, password, certStatusDTO);

        String statusName = CertStatusDTO.Status.getMsgByCode(certStatusDTO.getStatus());

        // 组装返回数据
        CertInfoDTO certInfo = new CertInfoDTO()
                .setStatus(certStatusDTO.getStatus())
                .setStatusName(statusName)
                .setSerialNumber(certStatusDTO.getCertSerialNumber())
                .setCreateTime(certStatusDTO.getCertCreateTime())
                .setExpireTime(certStatusDTO.getCertExpireTime())
                .setRevokeTime(certStatusDTO.getCertRevokeTime())
                .setRevokeReason(certStatusDTO.getCertRevokeReason())
                .setRemainingDays(certStatusDTO.getCertRemainingDays())
                .setName(getDnComponent(cert.getSubjectX500Principal().getName(), "CN"))
                .setTeamId(getTeamId(cert))
                .setIssuer(getDnComponent(cert.getIssuerX500Principal().getName(), "CN"))
                .setSubject(cert.getSubjectDN().toString())
                .setVersion(cert.getVersion())
                .setCountry(getDnComponent(cert.getSubjectX500Principal().getName(), "C"))
                .setSignatureAlgorithm(cert.getSigAlgName())
                .setValidFrom(cert.getNotBefore())
                .setValidTo(cert.getNotAfter());

        return certInfo;
    }

    // 解析DN中的指定组件
    private static String getDnComponent(String dn, String component) {
        try {
            LdapName ln = new LdapName(dn);
            for (Rdn rdn : ln.getRdns()) {
                if (rdn.getType().equalsIgnoreCase(component)) {
                    return rdn.getValue().toString();
                }
            }
        } catch (InvalidNameException e) {
            e.printStackTrace();
        }
        return ""; // 未找到返回空字符串
    }

    public static String getTeamId(X509Certificate cert) {
        try {
            // 解析Subject DN中的所有字段
            LdapName ln = new LdapName(cert.getSubjectX500Principal().getName());

            // 按优先级搜索字段：优先查找OU，其次UID
            List<String> candidateTypes = Arrays.asList("OU", "UID");
            for (String type : candidateTypes) {
                for (Rdn rdn : ln.getRdns()) {
                    if (rdn.getType().equalsIgnoreCase(type)) {
                        return rdn.getValue().toString();
                    }
                }
            }
        } catch (InvalidNameException e) {
            e.printStackTrace();
        }
        return null; // 未找到返回null
    }

//    public static String getIssuerCountry(X509Certificate cert) {
//        // 获取签发者的 X500Principal
//        X500Principal issuerPrincipal = cert.getIssuerX500Principal();
//
//        // 解析 DN 字符串（例如："C=US, O=Example Org, CN=Example CA"）
//        String issuerDN = issuerPrincipal.getName();
//
//        // 提取 "C" 字段（Country）
//        String[] dnComponents = issuerDN.split(",");
//        for (String component : dnComponents) {
//            component = component.trim();
//            if (component.startsWith("C=")) {
//                return component.substring(2); // 返回 "C=" 之后的值
//            }
//        }
//        return null; // 未找到国家信息
//    }

    public static String getIssuerCountry(X509Certificate cert) {
        // 将 Issuer DN 转换为 X500Name
        X500Name issuerName = new X500Name(cert.getIssuerX500Principal().getName());

        // 直接获取 "C" 字段
        return issuerName.getRDNs(BCStyle.C)[0].getFirst().getValue().toString();
    }
}